4.2 Go语言的链接与加载
Go
语言中,链接与加载是将编译后的多个目标文件组合成一个可执行文件,并最终在运行时加载该文件到内存中执行的过程。
在上节我们提到了汇编与链接,本节我们将详细探讨Go
的链接与加载机制。
本节代码存放目录为 lesson11
链接过程
链接是将多个编译单元(目标文件)组合成一个单独的可执行文件的过程。在Go
语言中,链接器(Linker
)负责完成这一任务。
简单来说,就是当我们编译时,每个Go
文件都会形成一个编译结果,链接就是将所有这些编译结果组合在一起的过程。
链接器在进行工作时,它的主要任务如下所示:
符号解析:链接器负责解析符号引用,确保每个引用都能找到正确的定义。例如,函数调用、全局变量的引用都需要通过符号解析来确定它们的实际位置。
地址重定位:在链接时,链接器会调整符号的地址,确保每个符号在最终的可执行文件中有正确的内存地址。
合并和优化:链接器将各个编译单元的代码和数据段合并,进行全局优化。例如:删除未使用的代码。
在Go
语言中链接器具有以下特点:
静态链接:
Go
默认使用静态链接,生成的可执行文件包含所有依赖的库,这使得部署更加方便。增量编译:支持增量编译,减少了大型项目中的编译时间。
内置Cgo支持:
Go
的链接器能够处理Cgo
代码,将C库
链接到Go
的可执行文件中。
链接的步骤如下所示:
将中间代码转换为汇编代码。
将汇编代码转换为机器指令。
将多个转换结果进行链接,形成最终的可执行文件。
可执行文件
生成可执行文件后,操作系统将加载该文件并将其映射到内存中。典型的内存布局如下:
代码段(.text):存放程序的指令。
数据段(.data):存放已初始化的全局变量和静态变量。
未初始化数据段(.bss):存放未初始化的全局变量和静态变量。
堆(heap):用于动态内存分配。
栈(stack):用于存放函数调用的局部变量、返回地址等。
接下来我们通过一个示例查看其最终的汇编实现,代码如下所示:
var (
globalVar = "I am a global variable"
)
func main() {
localVar := "I am a local variable"
fmt.Println(globalVar)
fmt.Println(localVar)
}
执行下面的命令进行编译:
go build -o example lesson11.go
编译完成后,会形成一个名为example
的可执行文件,我们执行下面的命令查看汇编实现:
go tool objdump -s main example
输出如下所示:
TEXT main.main(SB) /Users/xc/xcWork/YouCanGo/GoDeeper/GoDeeperCode/lesson11/lesson11.go
lesson11.go:9 0x100089f00 f9400b90 MOVD 16(R28), R16
lesson11.go:9 0x100089f04 eb3063ff CMP R16, RSP
lesson11.go:9 0x100089f08 54000589 BLS 44(PC)
lesson11.go:9 0x100089f0c f81a0ffe MOVD.W R30, -96(RSP)
lesson11.go:9 0x100089f10 f81f83fd MOVD R29, -8(RSP)
lesson11.go:9 0x100089f14 d10023fd SUB $8, RSP, R29
lesson11.go:11 0x100089f18 a904ffff STP (ZR, ZR), 72(RSP)
lesson11.go:11 0x100089f1c 90000562 ADRP 704512(PC), R2
lesson11.go:11 0x100089f20 9101c042 ADD $112, R2, R2
lesson11.go:11 0x100089f24 f9400040 MOVD (R2), R0
lesson11.go:11 0x100089f28 f9400441 MOVD 8(R2), R1
lesson11.go:11 0x100089f2c 97fdfa75 CALL runtime.convTstring(SB)
lesson11.go:11 0x100089f30 d0000142 ADRP 172032(PC), R2
lesson11.go:11 0x100089f34 912a8042 ADD $2720, R2, R2
lesson11.go:11 0x100089f38 f90027e2 MOVD R2, 72(RSP)
lesson11.go:11 0x100089f3c f9002be0 MOVD R0, 80(RSP)
从输出结果中我们可以看到汇编指令,通过上面的输出我们可以看到一个大概的情况。
加载过程是操作系统将可执行文件加载到内存中,并将其准备好执行的过程。主要步骤如下所示:
加载可执行文件:操作系统将可执行文件加载到内存中,并创建一个进程。
动态链接库的加载:如果程序依赖动态库,操作系统会加载这些库,并将它们映射到进程的地址空间中。
执行入口代码:操作系统将控制权交给可执行文件,程序从
main
函数开始执行。
小结
Go
语言的链接和加载过程是将源代码转换为可执行文件的关键步骤。通过理解链接器的工作原理、内存布局和加载过程,我们可以更好地优化和调试Go
程序。
关于本节总结如下:
通过链接将多个目标文件最终合并为一个可执行文件
程序启动时系统加载可执行文件到内存代码段中